<template>
  <div @keydown="handleInput" :contenteditable="contenteditable"
    :class="['bottom-sender', !content ? 'bottom-sender-empty' : '', className]" :style="[style, _style]" :data-placeholder="placeholder"
    enterkeyhint="send" ref="bottomSenderRef" @paste="handlePaste">
  </div>
</template>
<script lang="ts" setup>
import type { StyleValue } from 'vue';
import type { Exposed } from './type';

defineOptions({
  name: 'GpEditable',
  inheritAttrs: false
})
const emit = defineEmits<{
  pressEnter: [content: string]
}>()
const {
  placeholder,
  className,
  style,
  clearAfterPressEnter = true,
  disabled = false,
  readOnly = false
} = defineProps<{
  placeholder?: string
  className?: string
  style?: StyleValue
  /** @default true */
  clearAfterPressEnter?: boolean
  disabled?: boolean
  readOnly?: boolean
}>()
defineExpose<Exposed>({
  focus() {
    if (!contenteditable.value) return
    if (bottomSenderRef.value && document.activeElement !== bottomSenderRef.value) {
      bottomSenderRef.value.focus()
      if (content.value) {
        const selection = document.getSelection()
        if (selection && selection.rangeCount) {
          selection.selectAllChildren(bottomSenderRef.value)
          selection.collapseToEnd()
        }
      }
    }
  }
})
const content = defineModel<string>("content", { required: true })
const bottomSenderRef = ref<HTMLDivElement | null>(null)
const mo = ref<INullable<MutationObserver>>(null)
const contenteditable = computed(() => !disabled && !readOnly)
const _style = computed<StyleValue>(() => {
  if (disabled) {
    return {
      cursor: "not-allowed !important"
    }
  }
  return null
})
useEffectScope(() => {
  watch(content, () => {
    const chatInput = bottomSenderRef.value
    if (chatInput && !content.value) {
      chatInput.innerText = '';
    }
  })
})
onMounted(() => {
  observeSender()
})
onBeforeUnmount(() => {
  if (mo.value) {
    mo.value.disconnect()
  }
})
function observeSender() {
  const chatInput = bottomSenderRef.value
  if (chatInput) {
    mo.value = new MutationObserver(() => {
      content.value = chatInput.innerText.replace('\n', '')
    })
    mo.value.observe(chatInput, {
      subtree: true,
      characterData: true,
      childList: true
    })
  }
}
function handleInput(ev: KeyboardEvent) {
  if (ev.key === 'Enter') {
    ev.preventDefault();
    emit("pressEnter", content.value)
    if (clearAfterPressEnter) {
      const chatInput = bottomSenderRef.value!;
      chatInput.innerText = '';
      content.value = ""
    }
  }
}
function handlePaste(ev: ClipboardEvent) {
  ev.preventDefault()
  if (!contenteditable.value || !ev.clipboardData) return
  if (ev.clipboardData.types.includes('text/plain')) {
    const copiedText = ev.clipboardData.getData('text/plain')
    insertTextToCursor(copiedText)
  }
}
function insertTextToCursor(text: string) {
  const selection = document.getSelection()
  if (selection && selection.rangeCount > 0) {
    const range = selection.getRangeAt(0)
    range.deleteContents()
    range.collapse(false)
    const node = document.createTextNode(text)
    range.insertNode(node)
    range.setEndAfter(node)
    range.collapse(false)
    selection.removeAllRanges()
    selection.addRange(range)
  }
}
</script>
<style lang="less" scoped>
.bottom-sender {
  font-size: 14px;
  color: var(--color-primary);
  line-height: 20px;
  tab-size: 4;
  box-sizing: border-box;
  word-break: break-all;
  word-wrap: break-word;
  max-height: 100px;
  min-height: 20px;
  outline: none;
  overflow-y: auto;
  position: relative;

  &::before {
    color: var(--color-placeholder);
    pointer-events: none;
    position: absolute;
    left: 0px;
    right: 0px;
  }

  &.bottom-sender-empty::before {
    content: attr(data-placeholder);
  }
}
</style>